package io.github.libkodi.basic_gateway;

import java.net.URL;

import org.apache.log4j.PropertyConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.github.libkodi.basic_gateway.context.ApplicationContext;
import io.github.libkodi.basic_gateway.context.Context;
import io.github.libkodi.basic_gateway.router.RouteIntercepterImpl;
import io.github.libkodi.basic_gateway.router.RouterBuilder;
import io.github.libkodi.basic_gateway.tools.ClassFilter;
import io.github.libkodi.basic_gateway.tools.ClassUtils;
import io.vertx.config.ConfigRetriever;
import io.vertx.config.ConfigRetrieverOptions;
import io.vertx.config.ConfigStoreOptions;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Verticle;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;

public class MainVerticle extends AbstractVerticle {
	private Context ctx = null;
	private ClassFilter filter = null;
	
	private final static Logger log = LoggerFactory.getLogger(MainVerticle.class);
	
	@Override
	public void start(Promise<Void> startPromise) throws Exception {
		try {
			String packname = this.getClass().getPackage().getName();
			ClassLoader loader = this.getClass().getClassLoader();
			
			configLogger().onComplete(loggerConfigResult -> {
				if (loggerConfigResult.failed()) {
					loggerConfigResult.cause().printStackTrace(System.err);
				}
				
				ctx = new ApplicationContext(vertx);
				
				loadConfig(ctx, loader).onComplete(loadConfigResult -> {
					if (loadConfigResult.failed()) {
						log.error("Failed to load config", loadConfigResult.cause());
					}
					
					ClassUtils.scaner(loader, packname, ctx).onSuccess(filter -> {
						this.filter = filter;
						
						ClassUtils.injectField(ctx.components(), ClassFilter.name, filter, ClassFilter.class).onSuccess(v -> {
							filter.initVerticles(ctx).onSuccess(v6 -> {
								filter.execConfiguration(ctx).onSuccess(v1 -> {
									filter.initAllComponent(ctx).onSuccess(v2 -> {
										this.initDefaultServer(startPromise);
									}).onFailure(startPromise::fail);
								}).onFailure(startPromise::fail);
							}).onFailure(startPromise::fail);
						}).onFailure(startPromise::fail);
					}).onFailure(startPromise::fail);
				});
			});
		} catch (Exception e) {
			startPromise.fail(e);
		}
	}
	
	protected void initDefaultServer(Promise<Void> startPromise) {
		HttpServer server = vertx.createHttpServer();
		
		filter.createRouteIntercepters(ctx).onSuccess(intercepter -> {
			ClassUtils.injectField(ctx, ApplicationContext.ROUTE_INTERCEPTER_NAME, intercepter, RouteIntercepterImpl.class).onSuccess(v3 -> {
				Router router = Router.router(vertx);
				
				filter.execRouterConfiguraters(ctx, router).onSuccess(v6 -> {
					RouterBuilder.create(ctx, filter.getRouteList(), router).onSuccess(r -> {
						server.requestHandler(router);
					
						filter.createWebSockets(ctx, server).onSuccess(v5 -> {
							ctx.config("srever.host server.port").onSuccess(info -> {
								String host = info.getString("server.host", "0.0.0.0");
								int port = Integer.parseInt(info.getString("server.port", "8888"));
								
								server.listen(port, host).onComplete(ar -> {
									if (ar.succeeded()) {
										log.info("Program startup: " + host + ":" + port + "");
										startPromise.complete();
									} else {
										log.info("The program has been shutdown.");
										startPromise.fail(ar.cause());
									}
								});
							}).onFailure(startPromise::fail);
						}).onFailure(startPromise::fail);
					}).onFailure(startPromise::fail);
				}).onFailure(startPromise::fail);
			}).onFailure(startPromise::fail);
		}).onFailure(startPromise::fail);
	}

	private Future<Void> configLogger() {
		return Future.future(promise -> {
			String path = System.getProperty("config.logger.file");
			
			try {
				if (path != null) {
					PropertyConfigurator.configure(path);
				}
				
				promise.complete();
			} catch (Exception e) {
				promise.fail(e);
			}
		});
	}

	/**
	 * 加载配置文件信息
	 * 将用户指定的properties/json与系统属性统一加载到配置中，提供整个上下文可读取
	 * @param ctx 全局上下文
	 * @param loader 类加载器
	 * @return {@link Future<Void>}
	 */
	private Future<Void> loadConfig(Context ctx, ClassLoader loader) {
	  	return Future.future(promise -> {
	  		String path = null;
	  		
	  		try {
	  			path = System.getProperty("config.file");
	  			
	  			if (path == null) {
	  				URL url = loader.getResource("application.properties");
		  			
					if (url == null) {
						url = loader.getResource("application.json");
						
						if (url != null) {
							path = "application.json";
						}
					} else {
						path = "application.properties";
					}
	  			}
	  			
	  			if (path == null) {
	  				promise.fail("Profile was not found");
	  			} else {
	  				String type = null;
	  				
	  				if (path.endsWith(".properties")) {
	  					type = "properties";
	  				} else if (path.endsWith(".json")) {
	  					type = "json";
	  				}
	  				
	  				if (type == null) {
	  					promise.fail("Unknown profile type");
	  				} else {
	  					ConfigStoreOptions fileStore = new ConfigStoreOptions()
	  					.setType("file")
	  					.setFormat(type)
	  					.setConfig(new JsonObject().put("path", path));
	  					
	  					ConfigStoreOptions sysStore = new ConfigStoreOptions()
	  		  					.setType("sys")
	  		  					.setConfig(new JsonObject().put("raw-data", true)
	  		  											   .put("cache", true));
	  					
	  					long period = Long.parseLong(System.getProperty("config.scan.period", "0"));
	  					
	  					if (period > 0) {
	  						period = Math.max(period, 1000);
	  					}
	  					
	  					ConfigRetrieverOptions options = new ConfigRetrieverOptions()
	  					.setScanPeriod(period)
	  					.addStore(fileStore)
	  					.addStore(sysStore);
	  					
	  					ConfigRetriever retriever = ConfigRetriever.create(ctx.vertx(), options);
	  					
	  					retriever.getConfig().onSuccess(conf -> {
	  						ClassUtils.injectField(ctx, ApplicationContext.CONFIG_NAME, conf, JsonObject.class)
	  						.onSuccess(v -> {
	  							promise.complete();
	  						}).onFailure(e -> {
	  							promise.fail(e);
	  						});
	  					})
	  					.onFailure(e -> {
	  						promise.fail(e);
	  					});
	  					
	  					if (period > 0) {
	  						retriever.listen(change -> {
	  							ClassUtils.injectField(ctx, ApplicationContext.CONFIG_NAME, change.getNewConfiguration(), JsonObject.class)
		  						.onSuccess(v -> {
		  							promise.complete();
		  						}).onFailure(e -> {
		  							promise.fail(e);
		  						});
	  						});
	  					}
	  				}
	  			}
			} catch (Exception e) {
				promise.fail(e);
			}
	  	});
	}

	public static void run(Class<?> mainClass) {
		Vertx vertx = Vertx.vertx();
		
		try {
			vertx.deployVerticle((Verticle) ClassUtils.newInstance(mainClass))
			.onFailure(e -> {
				log.error("Runtime Error", e);
				vertx.close();
			});
		} catch (Exception e) {
			log.error("Runtime Error", e);
			vertx.close();
		}
	}
	
	@Override
	public void stop() throws Exception {
		super.stop();
		
		if (ctx != null) {
			try {
				ctx.components().removeAll().onFailure(e -> {
					log.error("Error by remove all component.", e);
				});
			} catch (Exception e) {
				log.error("Error: call Components.removeAll()", e);
			}
		}
	}
  
}
